package cn.kow.kmall.core.jpa;

import cn.kow.kmall.core.constant.KmallStatusConstant;
import cn.kow.kmall.core.entity.BaseEntity;
import cn.kow.kmall.core.jpa.annotion.QueryCondition;
import cn.kow.kmall.core.jpa.query.BasePageQuery;
import cn.kow.kmall.core.jpa.query.BaseQuery;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.criteria.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @Author: lijun
 * @Description:
 * @Date: Created in 2020-04-13 10:18
 * @Modified By:
 */
public class KmallJpaRepository<T extends BaseEntity, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseJpaRepository<T, ID> {

    private final JpaEntityInformation<T, ?> entityInformation;

    private EntityManager em;

    public KmallJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager em) {
        super(entityInformation, em);
        this.entityInformation = entityInformation;
        this.em = em;
    }

    public KmallJpaRepository(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.entityInformation = JpaEntityInformationSupport.getEntityInformation(domainClass, em);
        this.em = em;
    }

    @Override
    public T getById(ID id) {
        Optional<T> optional = findById(id);
        return optional.orElse(null);
    }

    @Override
    @Transactional
    public void delete(T entity) {
        Assert.notNull(entity, "Entity must not be null!");
        Object id = this.entityInformation.getId(entity);
        String queryString = QueryUtils.getQueryString("update %s x set deleted = true where id = ", this.entityInformation.getEntityName());
        this.em.createQuery(queryString + id).executeUpdate();
    }

    @Override
    @Transactional
    public void deleteAllById(Iterable<ID> ids) {
        Assert.notNull(ids, "Entities must not be null!");
        String queryString = QueryUtils.getQueryString("update %s x set deleted = true", this.entityInformation.getEntityName());
        StringBuilder builder = new StringBuilder(queryString).append(" where id in (");
        Iterator iterator = ids.iterator();
        while (iterator.hasNext()) {
            builder.append(iterator.next());
            if (iterator.hasNext()) {
                builder.append(",");
            }
        }
        builder.append(")");
        this.em.createQuery(builder.toString()).executeUpdate();
    }

    @Override
    @Transactional
    public T update(T entity) {
        entity.setUpdateTime(new Date());
        return this.save(entity);
    }

    @Override
    @Transactional
    public T create(T entity) {
        entity.setDeleted(false);
        entity.setCreateTime(new Date());
        entity.setUpdateTime(new Date());
        entity.setVersion(1L);
        if (null == entity.getStatus()) {
            entity.setStatus(KmallStatusConstant.NORMAL);
        }
        return this.save(entity);
    }

    @Override
    public <S extends T> List<S> listAll(S record) {
        record.setDeleted(false);
        Example<S> example = Example.of(record);
        return this.findAll(example);
    }

    @Override
    public <S extends T> List<S> listAll(S record, Sort sort) {
        record.setDeleted(false);
        Example<S> example = Example.of(record);
        return this.findAll(example, sort);
    }

    @Override
    public <S extends T> S getOne(S record) {
        record.setDeleted(false);
        Example<S> example = Example.of(record);
        return this.findOne(example).orElse(null);
    }

    @Override
    public T queryOne(BaseQuery query) {
        List<T> list = queryAll(query);
        return list != null ? list.get(0) : null;
    }

    @Override
    public Page<T> queryByPage(BasePageQuery query) {
        Pageable pageable = PageRequest.of(query.getPage()-1, query.getSize());
        return findAll(toSpecification(query), pageable);
    }

    @Override
    public List<T> queryAll(BaseQuery query) {
        return findAll(toSpecification(query));
    }

    private Specification<T> toSpecification(BaseQuery query) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder cb) {
                Class clazz = query.getClass();
                //获取查询类Query的所有字段,包括父类字段
                List<Field> fields = getAllFieldsWithRoot(clazz);
                List<Predicate> predicates = new ArrayList<>(fields.size());
                for (Field field : fields) {
                    //获取字段上的@QueryWord注解
                    QueryCondition condition = field.getAnnotation(QueryCondition.class);
                    if (condition == null) {
                        continue;
                    }
                    // 获取字段名
                    String column = condition.column();
                    // 如果主注解上colume为默认值"",则以field为准
                    if (column.equals("")) {
                        column = field.getName();
                    }
                    field.setAccessible(true);
                    try {
                        // nullable
                        Object value = field.get(query);
                        //如果值为null,注解未标注nullable,跳过
                        if (value == null && !condition.nullable()) {
                            continue;
                        }
                        // can be empty
                        if (value != null && String.class.isAssignableFrom(value.getClass())) {
                            String s = (String) value;
                            //如果值为"",且注解未标注emptyable,跳过
                            if (s.equals("") && !condition.emptiable()) {
                                continue;
                            }
                        }
                        //通过注解上func属性,构建路径表达式
                        Path path = root.get(column);
                        switch (condition.operator()) {
                            case equal:
                                predicates.add(cb.equal(path, value));
                                break;
                            case like:
                                predicates.add(cb.like(path, "%" + value + "%"));
                                break;
                            case gt:
                                predicates.add(cb.gt(path, (Number) value));
                                break;
                            case lt:
                                predicates.add(cb.lt(path, (Number) value));
                                break;
                            case ge:
                                predicates.add(cb.ge(path, (Number) value));
                                break;
                            case le:
                                predicates.add(cb.le(path, (Number) value));
                                break;
                            case notEqual:
                                predicates.add(cb.notEqual(path, value));
                                break;
                            case notLike:
                                predicates.add(cb.notLike(path, "%" + value + "%"));
                                break;
                            case greaterThan:
                                predicates.add(cb.greaterThan(path, (Comparable) value));
                                break;
                            case greaterThanOrEqualTo:
                                predicates.add(cb.greaterThanOrEqualTo(path, (Comparable) value));
                                break;
                            case lessThan:
                                predicates.add(cb.lessThan(path, (Comparable) value));
                                break;
                            case lessThanOrEqualTo:
                                predicates.add(cb.lessThanOrEqualTo(path, (Comparable) value));
                                break;
                            case in:
                                List<Object> list = (List<Object>) value;
                                CriteriaBuilder.In<Object> in = cb.in(path);
                                for (Object obj : list) {
                                    in.value(obj);
                                }
                                predicates.add(cb.and(cb.and(in)));
                                break;
                            case dateRange:
                                String[] range = ((String) value).split(",");
                                predicates.add(cb.greaterThanOrEqualTo(path.as(String.class), range[0]));
                                predicates.add(cb.lessThanOrEqualTo(path.as(String.class), range[1]));
                                break;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        continue;
                    }
                }
                // 排序
                String orderBy = query.getOrderBy();
                if (StringUtils.isNotBlank(orderBy)) {
                    List<Order> orders = new ArrayList<>();
                    for (String orderStr : orderBy.split(",")) {
                        if (orderStr.endsWith("asc")) {
                            orders.add(cb.asc(root.get(orderStr.split(" ")[0])));
                        } else {
                            orders.add(cb.desc(root.get(orderStr.split(" ")[0])));
                        }
                    }
                    criteriaQuery.orderBy(orders);
                }
                predicates.add(cb.equal(root.get("deleted"), false));
                Predicate p = cb.and(predicates.toArray(new Predicate[predicates.size()]));
                return p;
            }
        };
    }

    /**
     * 获取类clazz的所有Field，包括其父类的Field
     * @param clazz
     * @return
     */
    private List<Field> getAllFieldsWithRoot(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        Field[] dFields = clazz.getDeclaredFields();

        //获取本类所有字段
        if (null != dFields && dFields.length > 0) {
            fieldList.addAll(Arrays.asList(dFields));
        }

        // 若父类是Object，则直接返回当前Field列表
        Class<?> superClass = clazz.getSuperclass();
        if (superClass == Object.class) {
            return Arrays.asList(dFields);
        }
        // 递归查询父类的field列表
        List<Field> superFields = getAllFieldsWithRoot(superClass);
        if (null != superFields && !superFields.isEmpty()) {
            superFields.stream().
                    filter(field -> !fieldList.contains(field)).//不重复字段
                    forEach(field -> fieldList.add(field));
        }
        return fieldList;
    }
}
